Activating project at `/workspaces/Kalman filtering and smoothing`
Resolving package versions...
No Changes to `/workspaces/Kalman filtering and smoothing/Project.toml`
No Changes to `/workspaces/Kalman filtering and smoothing/Manifest.toml`
Pkg.status()
Status `/workspaces/Kalman filtering and smoothing/Project.toml`
[6e4b80f9] BenchmarkTools v1.8.0
[31c24e10] Distributions v0.25.131
[b964fa9f] LaTeXStrings v1.4.1
[91a5bcdd] Plots v1.41.7
⌅ [86711068] RxInfer v3.10.1
[860ef19b] StableRNGs v1.0.4
Info Packages marked with ⌅ have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated`
Kalman filtering and smoothing (Part 1)
This is an analysis of the RxInfer example at https://examples.rxinfer.com/categories/basic_examples/kalman_filtering_and_smoothing/
Some symbols have been changed
Some content has been added/modified
The preference is to make the math and code names align as much as possible
Spatial structure identifiers (e.g. vectors, matrices, cuboids) have a prefix _ (single underscore) in code and a boldface in the math (\(\mathbf{x}\))
Time sequence identifiers have a ‘’ (length mark) subscript in code (‘x’) and a colon subscript in the math (\(x_:\))
External (i.e. true, environment) states and parameters identifiers have a superscript x in code (vˣ) and a superscript * in the math (\(v^*\)). The ‘x’ in the code superscript is used to imitate superscript * in the math.
In the following set of examples the goal is to estimate hidden states of a Dynamical process where all hidden states are Gaussians.
We start our journey with a simple
multivariate Linear Gaussian State Space Model (LGSSM), which can be solved analytically. We then solve an
identification problem which does not have an analytical solution. Utimately, we show how RxInfer.jl can
deal with missing observations.
1 Gaussian Linear Dynamical System
LGSSM can be described with the following equations:
where \(\mathbf{x}_t\) are hidden states, \(\mathbf{y}_t\) are noisy observations, \(\mathbf{A}\), \(\mathbf{C}\) are state transition and observation matrices, \(\mathbf{\Sigma_x}\) and \(\mathbf{\Sigma_y}\) are state transition noise and observation noise covariance matrices. For a more rigorous introduction to Linear Gaussian Dynamical systems we refer to Simo Sarkka, Bayesian Filtering and Smoothing book.
seed =1234rng =MersenneTwister(seed)## We will model 2-dimensional observations with rotation matrix `Aˣ`## To avoid clutter we also assume that matrices `Aˣ`, `Cˣ`, `Σˣₓ`, and `Σˣᵧ`## are known and fixed for all time-steps_xˣ₀ = [ 10.0, -10.0 ]θˣ =π/35_Aˣ = [ cos(θˣ) -sin(θˣ); sin(θˣ) cos(θˣ) ]_Cˣ =diageye(2)_Σˣₓ =diageye(2)_Σˣᵧ =25.0.*diageye(2)T =300; ## number of observations
Next step, is to generate some synthetic data.
The Generative Process
State transition function (\(f_E\))
The state transition function provides the deterministic part of the state flow. The probabilistic part is provided by the system noise:
## Data comes from either a simulation/lab (sim|lab) OR from the field (fld)## Data are handled either in batches (batch) OR online as individual points (point)## Batch data accumulates either## along the depth/examples dimension/axis (into the screen/page), OR## typical for supervised & unsupervised learning## along the time dimension/axis (down the screen page)## typical for sequential decision learning (reinforcement learning & active inference)functionsim_batch_data(rng, T, _Aˣ, _Cˣ, _Qˣ, _Rˣ) ## simulated batch data _xˣₜ₋₁ = _xˣ₀ _fEː =Vector{Vector{Float64}}(undef, T) _xˣː =Vector{Vector{Float64}}(undef, T) _gEː =Vector{Vector{Float64}}(undef, T) _yˣː =Vector{Vector{Float64}}(undef, T)for t in1:T _fEː[t] =fE(_Aˣ=_Aˣ, _xˣₜ₋₁=_xˣₜ₋₁) _xˣː[t] =rand(rng, MvNormal(_fEː[t], _Σˣₓ)) _gEː[t] =gE(_Cˣ=_Cˣ, _xˣₜ=_xˣː[t]) _yˣː[t] =rand(rng, MvNormal(_gEː[t], _Σˣᵧ)) _xˣₜ₋₁ = _xˣː[t]endreturn _xˣː, _yˣːend
Let’s plot our synthetic dataset. Lines represent our hidden states we want to estimate using noisy observations, which are represented as dots.
p =plot(title="Hidden states with noisy observations")p =plot!(p, getindex.(_xˣː, 1), label="Hidden Signal "* L"x^*_1", color=:red)p =scatter!(p, getindex.(_yː, 1), label=false, markersize=2, color=:red)p =plot!(p, getindex.(_xˣː, 2), label="Hidden Signal "* L"x^*_2", color=:blue)p =scatter!(p, getindex.(_yː, 2), label=false, markersize=2, color=:blue)plot(p)
The Generative Model
To create a model we use GraphPPL package and @model macro:
@modelfunctionrotate_ssm(_yː, _x₀, _A, _C, _Σₓ, _Σᵧ) _x_prior ~MvNormalMeanCovariance(mean(_x₀), cov(_x₀)) _xₜ₋₁ = _x_priorfor t in1:length(_yː) _xː[t] ~MvNormalMeanCovariance(_A*_xₜ₋₁, _Σₓ) ## `_x` is a sequence of hidden states _yː[t] ~MvNormalMeanCovariance(_C*_xː[t], _Σᵧ) ## `_y` is a sequence of "clamped" observations _xₜ₋₁ = _xː[t]endend
To run inference we also specify a prior for our first hidden state:
## For large number of observations you need to use limit_stack_depth = 100 option during model creation, e.g. ## infer(..., options = (limit_stack_depth = 500, ))`## We assume the Aˣ, Cˣ, Σˣₓ, Σˣᵧ are known, i.e. not hidden result =infer( model=rotate_ssm(_x₀=_xˣ₀, _A=_Aˣ, _C=_Cˣ, _Σₓ=_Σˣₓ, _Σᵧ=_Σˣᵧ), data= (_yː = _yː,), free_energy=true);
xmarginals = result.posteriors[:_xː]logevidence =-result.free_energy; ## given the analytical solution, free energy will be equal to the negative log evidence
p =plot(title="Estimated states from noisy observations")p =plot!(p, getindex.(_xˣː, 1), label="Hidden Signal "* L"x^*_1", color=:red, linestyle=:dash)p =plot!(p, getindex.(_xˣː, 2), label="Hidden Signal "* L"x^*_2", color=:blue, linestyle=:dash)p =plot!(p, getindex.(mean.(xmarginals), 1), ribbon=getindex.(var.(xmarginals), 1) .|> sqrt, fillalpha=0.5, label="Estimated Signal "* L"x_1", color=:pink)p =plot!(p, getindex.(mean.(xmarginals), 2), ribbon=getindex.(var.(xmarginals), 2) .|> sqrt, fillalpha=0.5, label="Estimated Signal "* L"x_2", color=:lightblue)plot(p)
As we can see from our plot, estimated signal resembles closely to the real hidden states with small variance. We maybe also interested in the value for minus log evidence: